package pinboard.dataLayer.binPack;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;

import pinboard.dataLayer.ByteArrayConvert;

/*
支持字段类型为byte String int ,
byte[] String[] int[], List<String>
List<Object> Object的类序列化
 */

class BinSerializeMgr extends ObjectPackager {

    BinSerializeMgr(PackAttributeCache cache) {
        markCache = cache;
    }

    public byte[] toBytes(Object input, Class<?> tClass) throws IOException {
        if (isSimple(tClass)) {
            return simple2Bytes(input, tClass);
        } else {
            return object2Bytes(input);
        }
    }

    private byte[] object2Bytes(Object input) {
        if (input == null) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        List<PackAttributeFormat> list = markCache.get(input.getClass());
        for (PackAttributeFormat pbf : list) {
            try {
                Field fd = input.getClass().getField(pbf.fieldName);
                Object fdValue = fd.get(input);
                if (pbf.serializerName.length() > 0) {
                    IBinSerializer serializer = BinSerializerCache.get(pbf.serializerName);
                    addCustom(fdValue, serializer, pbf.fieldType, baos);
                } else if (pbf.isGroup) {
                    addGroup(fdValue, pbf.fieldType, pbf.fieldGenericType, baos);
                } else if (isSimple(pbf.fieldType)) {
                    addSimple(fdValue, pbf.fieldType, baos);
                } else {
                    //其他类型递归向下处理
                    object2BytesImp(fdValue, pbf.fixedRange, baos);
                }
            } catch (Exception ex) {

            }
        }
        return baos.toByteArray();
    }

    private void object2BytesImp(Object fdValue, int fixedRange, ByteArrayOutputStream baos) throws IOException {
        byte[] innerBytes = object2Bytes(fdValue);
        if (fixedRange < 0) {
            if (innerBytes != null) {
                baos.write(ByteArrayConvert.int2Bytes(innerBytes.length));//头长度
                baos.write(innerBytes);
            } else {
                baos.write(ByteArrayConvert.int2Bytes(0));
            }
        } else if (fixedRange == 0) {//不补充头长度
            if (innerBytes != null) {
                baos.write(innerBytes);
            } else {
                //do nothing
            }
        } else {
            if (innerBytes == null) {
                innerBytes = new byte[fixedRange];//无头，但指定总长度
            }
            baos.write(innerBytes);
        }
    }

    private void addCustom(Object o, IBinSerializer serializer, Class<?> tClass, ByteArrayOutputStream baos) throws IOException {
        if (tClass.isArray()) {
            byte[] bytes = serializer.toBytes((Object[]) o);
            baos.write(bytes);
        } else if (List.class.isAssignableFrom(tClass)) {
            List list = (List) o;
            byte[] bytes = serializer.toBytes(list.toArray());
            baos.write(bytes);
        } else {
            byte[] bytes = serializer.toBytes(o);
            baos.write(bytes);
        }
    }


    private byte[] simple2Bytes(Object input, Class<?> tClass) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        addSimple(input, tClass, baos);
        return baos.toByteArray();
    }

    private void addSimple(Object o, Class<?> tClass, ByteArrayOutputStream baos) throws IOException {
        if (tClass == byte.class) {
            addData((byte) o, baos);
        } else if (tClass == int.class) {
            addData((int) o, baos);
        } else if (tClass == String.class) {
            addData((String) o, baos);
        } else if (tClass == float.class) {
            addData((float) o, baos);
        } else if (tClass == long.class) {
            addData((long) o, baos);
        } else if (tClass == double.class) {
            addData((double) o, baos);
        }
    }

    //region byte int float String long double
    private void addData(byte b, ByteArrayOutputStream baos) {
        baos.write(b);
    }

    private void addData(int i, ByteArrayOutputStream baos) throws IOException {
        baos.write(ByteArrayConvert.int2Bytes(i));
    }

    private void addData(float f, ByteArrayOutputStream baos) throws IOException {
        baos.write(ByteArrayConvert.float2Bytes(f));
    }

    private void addData(long l, ByteArrayOutputStream baos) throws IOException {
        baos.write(ByteArrayConvert.long2Bytes(l));
    }

    private void addData(double d, ByteArrayOutputStream baos) throws IOException {
        baos.write(ByteArrayConvert.double2Bytes(d));
    }

    private void addData(String s, ByteArrayOutputStream baos) throws IOException {
        if (s == null || s.isEmpty()) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            byte[] data = s.getBytes(defaultCharsetName);
            baos.write(ByteArrayConvert.int2Bytes(data.length));
            baos.write(data);
        }
    }
    //endregion

    //region array of byte int float String long double
    private void addData(byte[] b, ByteArrayOutputStream baos) throws IOException {
        if (b == null || b.length == 0) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            baos.write(ByteArrayConvert.int2Bytes(b.length));
            baos.write(b);
        }
    }

    private void addData(int[] i, ByteArrayOutputStream baos) throws IOException {
        if (i == null || i.length == 0) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            baos.write(ByteArrayConvert.int2Bytes(i.length));
            for (int item : i) {
                baos.write(ByteArrayConvert.int2Bytes(item));
            }
        }
    }

    private void addData(float[] f, ByteArrayOutputStream baos) throws IOException {
        if (f == null || f.length == 0) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            baos.write(ByteArrayConvert.int2Bytes(f.length));
            for (float item : f) {
                baos.write(ByteArrayConvert.float2Bytes(item));
            }
        }
    }

    private void addData(long[] l, ByteArrayOutputStream baos) throws IOException {
        if (l == null || l.length == 0) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            baos.write(ByteArrayConvert.int2Bytes(l.length));
            for (long item : l) {
                baos.write(ByteArrayConvert.long2Bytes(item));
            }
        }
    }

    private void addData(double[] d, ByteArrayOutputStream baos) throws IOException {
        if (d == null || d.length == 0) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            baos.write(ByteArrayConvert.int2Bytes(d.length));
            for (double item : d) {
                baos.write(ByteArrayConvert.double2Bytes(item));
            }
        }
    }

    private void addData(String[] s, ByteArrayOutputStream baos) throws IOException {
        if (s == null || s.length == 0) {
            baos.write(ByteArrayConvert.int2Bytes(0));
        } else {
            baos.write(ByteArrayConvert.int2Bytes(s.length));
            for (String item : s) {
                byte[] data = item.getBytes(defaultCharsetName);
                baos.write(ByteArrayConvert.int2Bytes(data.length));
                baos.write(data);
            }
        }
    }
    //endregion

    private void addEmptyArray(ByteArrayOutputStream baos) throws IOException {
        baos.write(ByteArrayConvert.int2Bytes(0));
    }

    private void addObjectAsStringArray(Object[] arr, ByteArrayOutputStream baos) throws IOException {
        baos.write(ByteArrayConvert.int2Bytes(arr.length));
        for (int i = 0; i < arr.length; i++) {
            addData(((String) arr[i]).getBytes(defaultCharsetName), baos);
        }
    }

    private <T> void addArray(Object arr, Class<T> elementClass, ByteArrayOutputStream baos) throws IOException {
        if (arr != null) {
            T[] arrT = (T[]) arr;
            addData(arrT.length, baos);
            for (int i = 0; i < arrT.length; i++) {
                byte[] binData = toBytes(arrT[i], elementClass);
                addData(binData, baos);
            }
        }
    }

    private void addGroup(Object o, Class<?> tClass, Type gType, ByteArrayOutputStream baos) throws IOException {
        if (tClass.isArray()) {
            if (tClass == byte[].class) {
                addData((byte[]) o, baos);
            } else if (tClass == String[].class) {
                addData((String[]) o, baos);
            } else if (tClass == int[].class) {
                addData((int[]) o, baos);
            } else if (tClass == float[].class) {
                addData((float[]) o, baos);
            } else if (tClass == double[].class) {
                addData((double[]) o, baos);
            } else if (tClass == long[].class) {
                addData((long[]) o, baos);
            } else if (tClass == double[].class) {
                addData((double[]) o, baos);
            } else {
                addArray(o, tClass.getComponentType(), baos);
            }
        } else if (List.class.isAssignableFrom(tClass)) {
            if (o == null || ((List) o).isEmpty()) {
                addEmptyArray(baos);
            } else {
                List list = (List) o;
                Class<?> elementType = getFirstArgClass(gType);
                if (elementType == String.class) {
                    addObjectAsStringArray(list.toArray(), baos);
                } else {
                    addArray(list.toArray(), elementType, baos);
                }
            }
        }
    }
}

